/******************************************************************************/
/*  mbrtu.c                                                                   */
/*                                                                            */
/*  FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU. */
/*  Copyright (c) 2006 Christian Walter <wolti@sil.at>                        */
/*  All rights reserved.                                                      */
/******************************************************************************/

#include "data_types.h"
#include "Device\device.h"
#include "Usb\usb.h"
#include "TNKernel\tn.h"

#include "port\port.h"
#include "mb.h"
#include "mbrtu.h"
#include "mbframe.h"
#include "Lib\crc16.h"
#include "mbport.h"



/******************************************************************************/
/*      Defines                                                               */
/******************************************************************************/
#define MB_SER_PDU_SIZE_MIN     (4)       // Minimum size of a Modbus RTU frame
#define MB_SER_PDU_SIZE_MAX     (256)     // Maximum size of a Modbus RTU frame
#define MB_SER_PDU_SIZE_CRC     (2)       // Size of CRC field in PDU
#define MB_SER_PDU_ADDR_OFF     (0)       // Offset of slave address in Ser-PDU
#define MB_SER_PDU_PDU_OFF      (1)       // Offset of Modbus-PDU in Ser-PDU


/******************************************************************************/
/*      Type definitions                                                      */
/******************************************************************************/
typedef enum
{
    RTU_STATE_RX_INIT,          // Receiver is in initial state
    RTU_STATE_RX_IDLE,          // Receiver is in idle state
    RTU_STATE_RX_RCV,           // Frame is beeing received
    RTU_STATE_RX_ERROR          // If the frame is invalid
} eMB_RCV_STATE;


typedef enum
{
    RTU_STATE_TX_IDLE,          // Transmitter is in idle state
    RTU_STATE_TX_XMIT           // Transmitter is in transfer state
} eMB_SND_STATE;


/******************************************************************************/
/*      Static variables                                                      */
/******************************************************************************/
static volatile __eLINK_DEVICE s_link = eDEV_FREE;

static volatile eMB_SND_STATE s_sendState;
static volatile eMB_RCV_STATE s_rcvState;

static uint8_t s_rtuBuf[MB_SER_PDU_SIZE_MAX];

static uint8_t * s_pSendBufferCur;
static volatile uint16_t s_sendBufferCount;

static volatile uint32_t s_rcvBufferPos;



//==============================================================================
// App: MbRtuInit()
//==============================================================================
eMB_ERROR_CODE MbRtuInit(const uint8_t _slaveAddress,
                         const uint32_t _baudRate,
                         const eMB_PARITY _parity)
{
    eMB_ERROR_CODE status = MB_ENOERR;
    uint32_t timerT35_50us;

    (void)_slaveAddress;
    ENTER_CRITICAL_SECTION();

    // Modbus RTU uses 8 Databits
    if (xMbPortSerialInit(_baudRate, 8, _parity) != TRUE_T) {
        status = MB_EPORTERR;
    }
    else {
        // If baudrate > 19200 then we should use the fixed timer values
        // t35 = 1750us. Otherwise t35 must be 3.5 times the character time.
        if (_baudRate > 19200) {
            timerT35_50us = 35;       // 1800us
        }
        else {
            /*  The timer reload value for a character is given by:
             *
             *  timeValue = Ticks_per_1s / (Baudrate / 11)
             *             = 11 * Ticks_per_1s / Baudrate
             *             = 220000 / Baudrate
             *  The reload for t3.5 is 1.5 times this value and similary
             *  for t3.5.
             */
            timerT35_50us = (9UL * 220000UL) / (2UL * _baudRate);
        };

        if (xMbPortTimersInit((uint16_t)timerT35_50us) != TRUE_T) {
            status = MB_EPORTERR;
        };
    };

    EXIT_CRITICAL_SECTION();

    return status;
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: MbRtuStart()
//==============================================================================
void MbRtuStart(void)
{
    ENTER_CRITICAL_SECTION();

    /*  Initially the receiver is in the state STATE_RX_INIT. we start
     *  the timer and if no character is received within t3.5 we change
     *  to STATE_RX_IDLE. This makes sure that we delay startup of the
     *  modbus protocol stack until the bus is free.
     */
    s_rcvState = RTU_STATE_RX_INIT;
    vMbPortSerialEnable(TRUE_T, FALSE_T);
    vMbPortTimersEnable();

    EXIT_CRITICAL_SECTION();
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: MbRtuStop()
//==============================================================================
void MbRtuStop(void)
{
    ENTER_CRITICAL_SECTION();

    vMbPortSerialEnable(FALSE_T, FALSE_T);
    vMbPortTimersDisable();

    EXIT_CRITICAL_SECTION();
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: MbRtuReceive()
//==============================================================================
eMB_ERROR_CODE MbRtuReceive(uint8_t * _pRcvAddress,
                            uint8_t ** _pFrame,
                            uint16_t * _pLength)
{
    eMB_ERROR_CODE status = MB_ENOERR;

    ENTER_CRITICAL_SECTION();

    // Length and CRC check
    if ((s_rcvBufferPos >= MB_SER_PDU_SIZE_MIN)
        && (ModbusCrc16((uint8_t *)s_rtuBuf, s_rcvBufferPos) == 0))
    {
        /*  Save the address field. All frames are passed to the upper layed
         *  and the decision if a frame is used is done there.
         */
        *_pRcvAddress = s_rtuBuf[MB_SER_PDU_ADDR_OFF];

        /*  Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
         *  size of address field and CRC checksum.
         */
        *_pLength = (uint16_t)(s_rcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC);

        /*  Return the start of the Modbus PDU to the caller.
         */
        *_pFrame = (uint8_t *)(s_rtuBuf + MB_SER_PDU_PDU_OFF);
    }
    else {
        status = MB_EIO;
    };

    EXIT_CRITICAL_SECTION();

    return status;
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: MbRtuSend()
//==============================================================================
eMB_ERROR_CODE MbRtuSend(const uint8_t _slaveAddress,
                         const uint8_t * const _pFrame,
                         const uint16_t _length)
{
    eMB_ERROR_CODE status = MB_ENOERR;
    uint16_t Crc16;

    //ENTER_CRITICAL_SECTION();

    /*  Check if the receiver is still in idle state. If not we where to
     *  slow with processing the received frame and the master sent another
     *  frame on the network. We have to abort sending the frame.
     */
    if (s_rcvState == RTU_STATE_RX_IDLE) {

        // First byte before the Modbus-PDU is the slave address
        s_pSendBufferCur = (uint8_t *)(_pFrame - 1);
        s_sendBufferCount = 1;

        // Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU
        s_pSendBufferCur[MB_SER_PDU_ADDR_OFF] = _slaveAddress;
        s_sendBufferCount += _length;

        // Calculate CRC16 checksum for Modbus-Serial-Line-PDU
        Crc16 = ModbusCrc16((uint8_t *)s_pSendBufferCur, s_sendBufferCount);
        s_pSendBufferCur[s_sendBufferCount++] = (uint8_t)(Crc16 & 0x00FF);
        s_pSendBufferCur[s_sendBufferCount++] = (uint8_t)(Crc16 >> 8);

        // Activate the transmitter
        s_sendState = RTU_STATE_TX_XMIT;

        vMbPortSerialEnable(FALSE_T, TRUE_T);
    }
    else {
        status = MB_EIO;
    };

    //EXIT_CRITICAL_SECTION();

    return status;
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: xMbRtuReceiveFSM()
//==============================================================================
void xMbRtuSetLink(const __eLINK_DEVICE _link)
{
    // Save link
    s_link = _link;
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: xMbRtuReceiveFSM()
//==============================================================================
bool_t xMbRtuReceiveFSM(const uint8_t _xByte)
{
    bool_t xTaskNeedSwitch = FALSE_T;

    // Stop timer
    vMbPortTimersDisable();

    switch (s_rcvState)
    {
        //----------------------------------------------------------------------
        /*  If we have received a character in the init state we have to
         *  wait until the frame is finished.
         */
        case RTU_STATE_RX_INIT:
          vMbPortTimersEnable();
        break;

        //----------------------------------------------------------------------
        /*  In the error state we wait until all characters in the
         *  damaged frame are transmitted.
         */
        case RTU_STATE_RX_ERROR:
          vMbPortTimersEnable();
        break;

        //----------------------------------------------------------------------
        /*  In the idle state we wait for a new character. If a character
         *  is received the t1.5 and t3.5 timers are started and the
         *  receiver is in the state STATE_RX_RECEIVE.
         */
        case RTU_STATE_RX_IDLE:
          s_rcvBufferPos = 0;
          s_rtuBuf[s_rcvBufferPos++] = _xByte;
          s_rcvState = RTU_STATE_RX_RCV;
          xTaskNeedSwitch = TRUE_T;

          // Enable t3.5 timers
          vMbPortTimersEnable();
        break;

        //----------------------------------------------------------------------
        /* We are currently receiving a frame. Reset the timer after
         * every character received. If more than the maximum possible
         * number of bytes in a modbus frame is received the frame is
         * ignored.
         */
        case RTU_STATE_RX_RCV:
          if (s_rcvBufferPos < MB_SER_PDU_SIZE_MAX) {
              s_rtuBuf[s_rcvBufferPos++] = _xByte;
              xTaskNeedSwitch = TRUE_T;
          }
          else {
              s_rcvState = RTU_STATE_RX_ERROR;
          };
          vMbPortTimersEnable();
        break;

        //----------------------------------------------------------------------
        default:
        break;
    };

    return xTaskNeedSwitch;
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: xMbRtuTransmitFSM()
//==============================================================================
bool_t xMbRtuTransmitFSM(void)
{
    bool_t xNeedPoll = FALSE_T;

    switch (s_sendState)
    {
        //----------------------------------------------------------------------
        /*  We should not get a transmitter event if the transmitter is in
         *  idle state.
         */
        case RTU_STATE_TX_IDLE:
          // Enable receiver/disable transmitter
          vMbPortSerialEnable(TRUE_T, FALSE_T);
        break;

        //----------------------------------------------------------------------
        case RTU_STATE_TX_XMIT:
          // Check if we are finished
          if (s_sendBufferCount != 0) {
              if (s_link == eDEV_GSM) {
                  //
              }
              else {
                  uint32_t pattern;
                  sint32_t err;

                  ENTER_CRITICAL_SECTION();
                  USB::UsbTxDataBulk(s_pSendBufferCur, s_sendBufferCount);
                  EXIT_CRITICAL_SECTION();

                  err = tn_event_wait((TN_EVENT *)Get_modbus_poll_event(),
                                      MODBUS_TX_USB_EVENT,
                                      TN_EVENT_WCOND_AND,
                                      &pattern,
                                      TN_MSEC_TO_TICK(50));

                  if (err == TERR_NO_ERR) {
                      if (pattern == MODBUS_TX_USB_EVENT) {
                          xMbPortEventPostNoInt(MB_EV_FRAME_SENT);
                          vMbPortSerialEnable(TRUE_T, FALSE_T);
                      };
                  };

                  s_sendBufferCount = 0;
              };
              s_link = eDEV_FREE;
              xNeedPoll = TRUE_T;
          };
          s_sendState = RTU_STATE_TX_IDLE;
        break;

        //----------------------------------------------------------------------
        default:
        break;
    };

    return xNeedPoll;
}
//==============================================================================
//==============================================================================


//==============================================================================
// App: xMbRtuTimerT35Expired()
//==============================================================================
void xMbRtuTimerT35Expired(void)
{
    switch (s_rcvState)
    {
        //----------------------------------------------------------------------
        case RTU_STATE_RX_INIT:  // Timer t35 expired. Startup phase is finished
          xMbPortEventPost(MB_EV_READY);
        break;

        //----------------------------------------------------------------------
        /*  A frame was received and t35 expired. Notify the listener that
         *  a new frame was received.
         */
        case RTU_STATE_RX_RCV:
          xMbPortEventPost(MB_EV_FRAME_RECEIVED);
        break;

        //----------------------------------------------------------------------
        case RTU_STATE_RX_ERROR: // An error occured while receiving the frame
        break;

        //----------------------------------------------------------------------
        default:  // Function called in an illegal state
        break;
    }

    s_rcvState = RTU_STATE_RX_IDLE;
}
//==============================================================================
//==============================================================================